#include <defs.h>
#include <x86.h>
#include <stdio.h>
#include <string.h>
#include <mmu.h>
#include <memlayout.h>
#include <pmm.h>
#include <default_pmm.h>
#include <sync.h>
#include <error.h>

/* *
 * Task State Segment:任务状态段（TSS）
 *
 * The TSS may reside anywhere in memory. A special segment register called
 * the Task Register (TR) holds a segment selector that points a valid TSS
 * segment descriptor which resides in the GDT. Therefore, to use a TSS
 * the following must be done in function gdt_init:
 * TSS 可以位于内存的任何位置。一个特殊的段寄存器称为任务寄存器（TR），它保存一个段选择子，
 * 该选择子指向位于全局描述符表（GDT）中的有效 TSS 段描述符。
 * 因此，要使用 TSS，必须在 gdt_init 函数中完成以下操作：
 *   - create a TSS descriptor entry in GDT  在 GDT 中创建一个 TSS 描述符条目
 *   - add enough information to the TSS in memory as needed  根据需要将足够的信息添加到内存中的 TSS
 *   - load the TR register with a segment selector for that segment  使用该段的段选择子加载 TR 寄存器
 *
 * There are several fileds in TSS for specifying the new stack pointer when a
 * privilege level change happens. But only the fields SS0 and ESP0 are useful
 * in our os kernel.
 * TSS 中有几个字段用于指定特权级变化发生时的新栈指针。
 * 但在我们的操作系统内核中，仅有 SS0 和 ESP0 字段是有用的。
 *
 * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0
 * contains the new ESP value for CPL = 0. When an interrupt happens in protected
 * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value
 * into SS and ESP respectively.
 * 字段 SS0 包含 CPL = 0 的栈段选择子，而 ESP0 包含 CPL = 0 的新 ESP 值。当在保护模式下发生中断时，
 * x86 CPU 将在 TSS 中查找 SS0 和 ESP0，并将其值分别加载到 SS 和 ESP 中。
 * */
//静态任务状态结构体初始化为零
static struct taskstate ts = {0};

// virtual address of physicall page array声明了一个指向物理页面的数组的指针，用于管理物理内存中的页面。
struct Page *pages;
// amount of physical memory (in pages)用于记录物理内存的总量，以页面为单位，初始值为0。
size_t npage = 0;

// virtual address of boot-time page directory
extern pde_t __boot_pgdir; //声明了一个外部变量，表示启动时的页目录。
pde_t *boot_pgdir = &__boot_pgdir; //将其指针赋值给 boot_pgdir
// physical address of boot-time page directory
// 用于存储启动时页目录的物理地址，这通常用于内存管理中的页表转换
uintptr_t boot_cr3;

// physical memory management
//声明了一个指向物理内存管理器结构的指针，用于管理和分配物理内存。
const struct pmm_manager *pmm_manager;

/* *
 * The page directory entry corresponding to the virtual address range
 * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page
 * directory is treated as a page table as well as a page directory.
 * 对应于虚拟地址范围 [VPT, VPT + PTSIZE) 的页目录项指向页目录本身。
 * 因此，页目录既被视为页表，也被视为页目录。
 * 
 * One result of treating the page directory as a page table is that all PTEs
 * can be accessed though a "virtual page table" at virtual address VPT. And the
 * PTE for number n is stored in vpt[n].
 * 将页目录视为页表的一个结果是，所有的页表项（PTE）可以通过位于虚拟地址 VPT 的“虚拟页表”进行访问。
 * 页表项编号为 n 的项存储在 vpt[n] 中。
 * 
 * A second consequence is that the contents of the current page directory will
 * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which
 * vpd is set bellow.
 * 第二个结果是，当前页目录的内容总是可以在虚拟地址 PGADDR(PDX(VPT), PDX(VPT), 0) 处获得，
 * vpd 将被设置为该地址。
 * */
pte_t * const vpt = (pte_t *)VPT; //定义了一个常量指针 vpt，指向虚拟地址 VPT，该地址表示虚拟页表。
pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0);//定义了另一个常量指针 vpd，指向当前页目录的虚拟地址。
//PGADDR 是一个宏或函数，用于生成特定格式的虚拟地址，其中 PDX 可能用于计算虚拟地址的索引部分。
/* *
 * Global Descriptor Table:
 * 全局描述符表
 * The kernel and user segments are identical (except for the DPL). To load
 * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the
 * segments for the user and the kernel. Defined as follows:
 * 内核和用户段是相同的（除了特权级 DPL ）。要加载 %ss 寄存器，当前特权级 CPL 必须等于 DPL。
 * 因此，我们必须为用户和内核重复这些段。定义如下：
 *   - 0x0 :  unused (always faults -- for trapping NULL far pointers)未使用（始终发生错误 -- 用于捕获 NULL 远指针）
 *   - 0x8 :  kernel code segment 内核代码段
 *   - 0x10:  kernel data segment 内核数据段
 *   - 0x18:  user code segment 用户代码段
 *   - 0x20:  user data segment 用户数据段
 *   - 0x28:  defined for tss, initialized in gdt_init 定义用于 TSS，在 gdt_init 中初始化
 * */
//定义了一个静态的全局描述符表（GDT），用于管理内核和用户的段描述符。
static struct segdesc gdt[] = {
    SEG_NULL,//第一个段描述符，保留为 NULL，用于捕获无效的段访问。
    //内核代码段，具有执行（STA_X）和可读（STA_R）属性，基地址为 0x0，大小为 0xFFFFFFFF，特权级为内核（DPL_KERNEL）。
    [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL),
    //内核数据段，具有可写（STA_W）属性，基地址为 0x0，大小为 0xFFFFFFFF，特权级为内核（DPL_KERNEL）。
    [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL),
    //用户代码段，具有执行（STA_X）和可读（STA_R）属性，基地址为 0x0，大小为 0xFFFFFFFF，特权级为用户（DPL_USER）。
    [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER),
    //用户数据段，具有可写（STA_W）属性，基地址为 0x0，大小为 0xFFFFFFFF，特权级为用户（DPL_USER）。
    [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER),
    [SEG_TSS]   = SEG_NULL,//任务状态段（TSS），保留为 NULL，后续会在初始化时设置。
};
//定义了一个静态的伪描述符，用于描述 GDT 的长度和地址。
static struct pseudodesc gdt_pd = {
    sizeof(gdt) - 1, (uintptr_t)gdt
};

static void check_alloc_page(void);//声明一个静态函数，用于检查内存页的分配。
static void check_pgdir(void); //声明一个静态函数，用于检查页目录的有效性。
static void check_boot_pgdir(void); //声明一个静态函数，用于检查启动时的页目录。

/* *
 * lgdt - load the global descriptor table register and reset the
 * data/code segement registers for kernel.
 * lgdt - 加载全局描述符表寄存器并重置内核的数据/代码段寄存器。
 * */
//定义了一个静态内联函数 lgdt，接收一个指向伪描述符（struct pseudodesc）的指针 pd
static inline void
lgdt(struct pseudodesc *pd) {
    asm volatile ("lgdt (%0)" :: "r" (pd));//这行汇编代码使用 lgdt 指令加载 GDT。%0 被替换为指向 pd 的指针，告诉处理器 GDT 的地址。
    asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS));//将 USER_DS（用户数据段）的值移动到 gs 段寄存器。
    asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS));//将 USER_DS 的值移动到 fs 段寄存器。
    asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS));//将 KERNEL_DS（内核数据段）的值移动到 es 段寄存器。
    asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS));//将 KERNEL_DS 的值移动到 ds 段寄存器
    asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS));//将 KERNEL_DS 的值移动到 ss 段寄存器
    // reload cs
    //通过 ljmp 指令重新加载代码段寄存器 cs，并跳转到标签 1。
    asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS));
}

/* *
 * load_esp0 - change the ESP0 in default task state segment,
 * so that we can use different kernel stack when we trap frame
 * user to kernel.
 *  load_esp0 - 修改默认任务状态段中的 ESP0，以便在从用户态陷入内核态时能够使用不同的内核栈。
 * */
//uintptr_t esp0：这是新的堆栈指针，通常指向内核栈的顶部。
//修改当前任务状态段（TSS）中的 ESP0 值。ESP0 是在从用户态切换到内核态时，CPU 使用的内核栈指针。
void
load_esp0(uintptr_t esp0) {
    ts.ts_esp0 = esp0;
}

/* gdt_init - initialize the default GDT and TSS */
/* gdt_init - 初始化默认的 GDT 和 TSS */
static void
gdt_init(void) {
    // 设置启动内核栈和默认的 SS0
    // set boot kernel stack and default SS0
    load_esp0((uintptr_t)bootstacktop);
    ts.ts_ss0 = KERNEL_DS;
    // 初始化 GDT 中的 TSS 字段
    // initialize the TSS filed of the gdt
    gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL);
    // 使用lgdt加载全局描述符表，更新所有段寄存器
    // reload all segment registers
    lgdt(&gdt_pd);
    // 加载 TSS，使 CPU 在进行特权级切换时能够正确使用 TSS。
    // load the TSS
    ltr(GD_TSS);
}

//init_pmm_manager - initialize a pmm_manager instance
//初始化一个 pmm_manager 实例
static void
init_pmm_manager(void) {
    //将 pmm_manager 指向默认的 PMM 管理器实例。
    pmm_manager = &default_pmm_manager;
    //使用 cprintf 打印当前内存管理器的名称。
    cprintf("memory management: %s\n", pmm_manager->name);
    //调用 PMM 管理器的初始化函数，以设置和准备内存管理的相关数据结构。
    pmm_manager->init();
}

//init_memmap - call pmm->init_memmap to build Page struct for free memory  
// init_memmap - 调用 pmm->init_memmap 构建空闲内存的 Page 结构
//struct Page *base：指向内存页的基础地址。 size_t n：要初始化的页数。
static void
init_memmap(struct Page *base, size_t n) {
    pmm_manager->init_memmap(base, n);
}

//alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory 
// alloc_pages - 调用 pmm->alloc_pages 分配连续的 n*PAGESIZE 内存
struct Page *
alloc_pages(size_t n) {
    struct Page *page=NULL;
    bool intr_flag;
    //使用 local_intr_save 保存当前的中断状态，以避免在分配内存时发生中断。
    local_intr_save(intr_flag);
    {
        //调用物理内存管理器的 alloc_pages 函数分配 n 页的内存。
        page = pmm_manager->alloc_pages(n);
    }
    //恢复之前保存的中断状态。
    local_intr_restore(intr_flag);
    return page;
}

//free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory 
// free_pages - 调用 pmm->free_pages 释放连续的 n*PAGESIZE 内存
//struct Page *base：指向要释放的内存页的基础地址。size_t n：要释放的页数。
void
free_pages(struct Page *base, size_t n) {
    bool intr_flag;
    //使用 local_intr_save 保存当前的中断状态，以避免在释放内存时发生中断。
    local_intr_save(intr_flag);
    {
        //调用物理内存管理器的 free_pages 函数释放 n 页的内存。
        pmm_manager->free_pages(base, n);
    }
    local_intr_restore(intr_flag);
}

//nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) 
//of current free memory
// nr_free_pages - 调用 pmm->nr_free_pages 获取当前空闲内存的大小 (nr * PAGESIZE)
size_t
nr_free_pages(void) {
    size_t ret;// 定义变量 ret 用于存储返回的空闲内存大小
    bool intr_flag;// 定义变量 intr_flag 用于保存中断状态
    local_intr_save(intr_flag);// 保存当前中断状态，并禁用中断
    {
        ret = pmm_manager->nr_free_pages();// 调用物理内存管理器的函数获取空闲内存页数
    }
    local_intr_restore(intr_flag);// 恢复之前保存的中断状态
    return ret;// 返回空闲内存的大小
}

/* pmm_init - initialize the physical memory management */
/* pmm_init - 初始化物理内存管理 */
static void
page_init(void) {
    // 获取物理内存映射信息，存于特定地址
    struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE);
    uint64_t maxpa = 0;// 初始化最大物理地址为0

    cprintf("e820map:\n");// 打印“e820map”标题
    int i;
    for (i = 0; i < memmap->nr_map; i ++) {// 遍历内存映射数组
        uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; // 获取每个区域的起始和结束地址
        cprintf("  memory: %08llx, [%08llx, %08llx], type = %d.\n",// 打印内存区域的信息
                memmap->map[i].size, begin, end - 1, memmap->map[i].type);
        //type=1表示可用的 RAM 区域；type=2表示保留的区域，不可用。
        if (memmap->map[i].type == E820_ARM) { // 检查内存类型是否为可用内存
            if (maxpa < end && begin < KMEMSIZE) {// 检查当前区域是否在有效范围内
                maxpa = end;// 更新最大物理地址
            }
        }
    }
    if (maxpa > KMEMSIZE) {// 如果最大物理地址超过了预定义的内存上限
        maxpa = KMEMSIZE;// 将其限制为内存上限
    }

    extern char end[];// 引入全局变量 end，指向内存的结束位置

    npage = maxpa / PGSIZE; // 计算可用页数
    pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);// 将 end 对齐到页边界，指向页结构数组的开头

    for (i = 0; i < npage; i ++) {// 遍历每一页
        SetPageReserved(pages + i);// 将每一页标记为保留状态
    }

    uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage);// 计算可用内存的起始地址

    for (i = 0; i < memmap->nr_map; i ++) {// 再次遍历内存映射
        uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size;// 获取每个区域的起始和结束地址
        if (memmap->map[i].type == E820_ARM) {// 如果区域类型为可用内存
            if (begin < freemem) { // 如果起始地址小于可用内存地址
                begin = freemem;// 将起始地址设置为可用内存地址
            }
            if (end > KMEMSIZE) {// 如果结束地址超过内存上限
                end = KMEMSIZE;// 将其限制为内存上限
            }
            if (begin < end) {// 如果起始地址小于结束地址
                begin = ROUNDUP(begin, PGSIZE);// 将起始地址对齐到页边界
                end = ROUNDDOWN(end, PGSIZE);// 将结束地址对齐到页边界
                if (begin < end) {// 如果调整后的起始地址仍小于结束地址
                    init_memmap(pa2page(begin), (end - begin) / PGSIZE);// 初始化内存页映射
                }
            }
        }
    }
}

//boot_map_segment - setup&enable the paging mechanism
// parameters
// boot_map_segment - 设置并启用分页机制
//  la:   linear address of this memory need to map (after x86 segment map)
//la:   需要映射的线性地址（经过 x86 段映射后的地址）
//  size: memory size   size: 内存大小
//  pa:   physical address of this memory  pa:该内存的物理地址
//  perm: permission of this memory    perm: 该内存的权限
static void
boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) {
    // 确保线性地址和物理地址的页偏移相同
    assert(PGOFF(la) == PGOFF(pa));
    // 计算需要映射的页数，ROUNDUP 将总大小对齐到下一个页大小的边界
    size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE;
    // 将线性地址向下对齐到页边界
    la = ROUNDDOWN(la, PGSIZE);
    // 将物理地址向下对齐到页边界
    pa = ROUNDDOWN(pa, PGSIZE);
    // 循环遍历每一页，直到映射的页数为零
    for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) {
        // 获取当前页的页表项指针，如果不存在则创建新的页表项
        pte_t *ptep = get_pte(pgdir, la, 1);
        // 确保页表项指针不为空
        assert(ptep != NULL);
         // 设置页表项，包含物理地址、存在位和权限
        *ptep = pa | PTE_P | perm;
    }
}

//boot_alloc_page - allocate one page using pmm->alloc_pages(1) 
// return value: the kernel virtual address of this allocated page
//note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table)
//boot_alloc_page - 使用 pmm->alloc_pages(1) 分配一页内存.返回值: 分配的页面的内核虚拟地址
//注意: 此函数用于获取页目录表(PDT)和页表(PT)的内存
static void *
boot_alloc_page(void) {
    struct Page *p = alloc_page();// 调用分配页面的函数
    if (p == NULL) {// 检查分配是否成功
        panic("boot_alloc_page failed.\n");// 如果分配失败，则触发异常
    }
    return page2kva(p);// 返回分配页面的内核虚拟地址
}

//pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism 
//         - check the correctness of pmm & paging mechanism, print PDT&PT
//pmm_init - 设置物理内存管理器，构建页目录表(PDT)和页表(PT)，以设置分页机制
//           - 检查物理内存管理器和分页机制的正确性，打印页目录表和页表
void
pmm_init(void) {
    // We've already enabled paging
    // 我们已经启用了分页
    boot_cr3 = PADDR(boot_pgdir);

    //We need to alloc/free the physical memory (granularity is 4KB or other size). 
    //So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h
    //First we should init a physical memory manager(pmm) based on the framework.
    //Then pmm can alloc/free the physical memory. 
    //Now the first_fit/best_fit/worst_fit/buddy_system pmm are available.
     // 我们需要分配/释放物理内存（粒度为 4KB 或其他大小）。
    // 因此在 pmm.h 中定义了物理内存管理器的框架（struct pmm_manager）。
    // 首先，我们应该基于该框架初始化一个物理内存管理器(pmm)。
    // 然后 pmm 可以分配/释放物理内存。
    // 现在，first_fit/best_fit/worst_fit/buddy_system 的 pmm 都可用。
    init_pmm_manager();// 初始化物理内存管理器

    // detect physical memory space, reserve already used memory,
    // then use pmm->init_memmap to create free page list
    // 检测物理内存空间，保留已经使用的内存，
    // 然后使用 pmm->init_memmap 创建空闲页面列表
    page_init();// 初始化页面管理

    //use pmm->check to verify the correctness of the alloc/free function in a pmm
    // 使用 pmm->check 验证 pmm 中分配/释放函数的正确性
    check_alloc_page();// 检查页面分配功能

    check_pgdir();// 检查页目录的状态

    static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0);// 确保 KERNBASE 和 KERNTOP 是页大小的倍数

    // recursively insert boot_pgdir in itself
    // to form a virtual page table at virtual address VPT
    // 递归地将 boot_pgdir 插入到自身中
    // 在虚拟地址 VPT 处形成虚拟页表
    boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W;// 设置页目录项，映射自身

    // map all physical memory to linear memory with base linear addr KERNBASE
    // linear_addr KERNBASE ~ KERNBASE + KMEMSIZE = phy_addr 0 ~ KMEMSIZE
    // 将所有物理内存映射到线性内存，基地址为 KERNBASE
    // 线性地址 KERNBASE ~ KERNBASE + KMEMSIZE = 物理地址 0 ~ KMEMSIZE
    boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W);// 映射物理内存

    // Since we are using bootloader's GDT,
    // we should reload gdt (second time, the last time) to get user segments and the TSS
    // map virtual_addr 0 ~ 4G = linear_addr 0 ~ 4G
    // then set kernel stack (ss:esp) in TSS, setup TSS in gdt, load TSS
    // 由于我们正在使用引导加载程序的 GDT，
    // 我们应该重新加载 GDT（第二次，也是最后一次），以获取用户段和 TSS
    // 映射虚拟地址 0 ~ 4G = 线性地址 0 ~ 4G
    // 然后在 TSS 中设置内核栈 (ss:esp)，在 gdt 中设置 TSS，加载 TSS
    gdt_init();// 初始化全局描述符表

    //now the basic virtual memory map(see memalyout.h) is established.
    //check the correctness of the basic virtual memory map.
     // 现在基本的虚拟内存映射（见 memlayout.h）已建立。
    // 检查基础虚拟内存映射的正确性。
    check_boot_pgdir(); // 检查页目录的正确性

    print_pgdir(); // 打印页目录表

}

//get_pte - get pte and return the kernel virtual address of this pte for la
//        - if the PT contians this pte didn't exist, alloc a page for PT
// parameter:
//  pgdir:  the kernel virtual base address of PDT
//  la:     the linear address need to map
//  create: a logical value to decide if alloc a page for PT
// return vaule: the kernel virtual address of this pte
// get_pte - 获取页表项（PTE），并返回该 PTE 的内核虚拟地址
//           如果页表中不存在该 PTE，则为页表分配一页
// 参数：
//  pgdir:  页目录的内核虚拟基地址
//  la:     需要映射的线性地址
//  create: 一个逻辑值，决定是否为页表分配一页
// 返回值：该 PTE 的内核虚拟地址
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
    /* LAB2 EXERCISE 2: YOUR CODE
     *
     * If you need to visit a physical address, please use KADDR()
     * please read pmm.h for useful macros
     *
     * Maybe you want help comment, BELOW comments can help you finish the code
     *
     * Some Useful MACROs and DEFINEs, you can use them in below implementation.
     * MACROs or Functions:
     *   PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la.
     *   KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address.
     *   set_page_ref(page,1) : means the page be referenced by one time
     *   page2pa(page): get the physical address of memory which this (struct Page *) page  manages
     *   struct Page * alloc_page() : allocation a page
     *   memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s
     *                                       to the specified value c.
     * DEFINEs:
     *   PTE_P           0x001                   // page table/directory entry flags bit : Present
     *   PTE_W           0x002                   // page table/directory entry flags bit : Writeable
     *   PTE_U           0x004                   // page table/directory entry flags bit : User can access
     */
#if 0
    pde_t *pdep = NULL;   // (1) find page directory entry
    if (0) {              // (2) check if entry is not present
                          // (3) check if creating is needed, then alloc page for page table
                          // CAUTION: this page is used for page table, not for common data page
                          // (4) set page reference
        uintptr_t pa = 0; // (5) get linear address of page
                          // (6) clear page content using memset
                          // (7) set page directory entry's permission
    }
    return NULL;          // (8) return page table entry
#endif
    // (1) 找到页目录项
    pde_t *pdep = &pgdir[PDX(la)];// 使用 PDX 宏获取页目录索引
    // (2) 检查页目录项是否存在
    if (!(*pdep & PTE_P)) {// 如果页目录项的存在位 PTE_P 没有被设置
        struct Page *page;// 声明一个指针，用于指向新分配的页面
        // 检查是否允许创建新页表，或者分配页表失败
        if (!create || (page = alloc_page()) == NULL) {// 如果不允许创建或分配失败
            return NULL;// 返回 NULL，表示无法获取页表
        }
        // 设置新分配页面的引用计数为 1
        set_page_ref(page, 1);
        uintptr_t pa = page2pa(page);// 获取新分配页面的物理地址
        memset(KADDR(pa), 0, PGSIZE);// 清空新分配的页表内容，初始化为零
        // 更新页目录项，设置物理地址和权限位
        *pdep = pa | PTE_U | PTE_W | PTE_P;// 将物理地址和权限位（用户可访问、可写、有效）合并设置
    }
    // 返回指定线性地址 la 对应的页表项的内核虚拟地址
    return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];// 计算并返回页表项的指针
}

//get_page - get related Page struct for linear address la using PDT pgdir
// get_page - 获取与线性地址 la 相关的 Page 结构体，使用页目录 pgdir
struct Page *
get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) {
     // 调用 get_pte 函数获取对应线性地址 la 的页表项指针
    pte_t *ptep = get_pte(pgdir, la, 0);
    // 如果 ptep_store 指针不为 NULL，将 ptep 存储到 ptep_store 指向的位置
    if (ptep_store != NULL) {
        *ptep_store = ptep; // 存储当前页表项的指针
    }
    // 检查 ptep 是否有效以及页表项的存在位 PTE_P 是否被设置
    if (ptep != NULL && *ptep & PTE_P) {
        // 返回与页表项对应的 Page 结构体
        return pte2page(*ptep);// 将页表项转换为对应的 Page 结构
    }
    // 如果未找到有效的页，返回 NULL
    return NULL;
}

//page_remove_pte - free an Page sturct which is related linear address la
//                - and clean(invalidate) pte which is related linear address la
//note: PT is changed, so the TLB need to be invalidate 
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
    /* LAB2 EXERCISE 3: YOUR CODE
     *
     * Please check if ptep is valid, and tlb must be manually updated if mapping is updated
     *
     * Maybe you want help comment, BELOW comments can help you finish the code
     *
     * Some Useful MACROs and DEFINEs, you can use them in below implementation.
     * MACROs or Functions:
     *   struct Page *page pte2page(*ptep): get the according page from the value of a ptep
     *   free_page : free a page
     *   page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free.
     *   tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being
     *                        edited are the ones currently in use by the processor.
     * DEFINEs:
     *   PTE_P           0x001                   // page table/directory entry flags bit : Present
     */
#if 0
    if (0) {                      //(1) check if this page table entry is present
        struct Page *page = NULL; //(2) find corresponding page to pte
                                  //(3) decrease page reference
                                  //(4) and free this page when page reference reachs 0
                                  //(5) clear second page table entry
                                  //(6) flush tlb
    }
#endif
    if (*ptep & PTE_P) {
        struct Page *page = pte2page(*ptep);// 找到对应的物理页
        // 减少物理页的引用计数，如果引用计数为零，释放该物理页
        if (page_ref_dec(page) == 0) {
            free_page(page);
        }
        *ptep = 0;// 清除页表项
        tlb_invalidate(pgdir, la);// 刷新 TLB
    }
}

//page_remove - free an Page which is related linear address la and has an validated pte
//移除一个虚拟地址对应的页面
void
page_remove(pde_t *pgdir, uintptr_t la) {
    //调用 get_pte 函数获取给定虚拟地址 la 对应的页表项指针 ptep。
    pte_t *ptep = get_pte(pgdir, la, 0);
    //如果 ptep 不为 NULL，则调用 page_remove_pte 函数移除该页表项。
    if (ptep != NULL) {
        page_remove_pte(pgdir, la, ptep);
    }
}

//page_insert - build the map of phy addr of an Page with the linear addr la
// paramemters:
//  pgdir: the kernel virtual base address of PDT
//  page:  the Page which need to map
//  la:    the linear address need to map
//  perm:  the permission of this Page which is setted in related pte
// return value: always 0
//note: PT is changed, so the TLB need to be invalidate 
//将一个页面插入到页表中。
int
page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) {
    //通过 get_pte 函数获取指定虚拟地址 la 对应的页表项指针 ptep。
    pte_t *ptep = get_pte(pgdir, la, 1);
    //如果 ptep 为 NULL，表示内存分配失败，返回 -E_NO_MEM。
    if (ptep == NULL) {
        return -E_NO_MEM;
    }
    //调用 page_ref_inc 增加页面的引用计数。
    page_ref_inc(page);
    //如果页表项已存在且指向当前页面，则减少页面引用计数。
    if (*ptep & PTE_P) {
        struct Page *p = pte2page(*ptep);
        if (p == page) {
            page_ref_dec(page);
        }
        //如果页表项已存在但指向其他页面，则调用 page_remove_pte 移除旧的页表项。
        else {
            page_remove_pte(pgdir, la, ptep);
        }
    }
    *ptep = page2pa(page) | PTE_P | perm;
    tlb_invalidate(pgdir, la);//刷新 TLB
    return 0;
}

// invalidate a TLB entry, but only if the page tables being
// edited are the ones currently in use by the processor.
//无效化指定地址的TLB条目
void
tlb_invalidate(pde_t *pgdir, uintptr_t la) {
    //检查当前页目录地址是否与传入的页目录地址相同。
    if (rcr3() == PADDR(pgdir)) {
        //如果相同，则调用 invlpg 函数无效化指定线性地址的TLB条目。
        invlpg((void *)la);
    }
}

static void
check_alloc_page(void) {
    //调用内存管理器的 check 方法，用于检查内存分配是否正常。
    pmm_manager->check();
    cprintf("check_alloc_page() succeeded!\n");
}
//用于验证页目录和页表的正确性。
static void
check_pgdir(void) {
    //确保内存页面数量在合理范围内
    assert(npage <= KMEMSIZE / PGSIZE);
    //确保页目录不为空且对齐，
    assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0);
    //确保虚拟地址 0x0 没有映射任何页面
    assert(get_page(boot_pgdir, 0x0, NULL) == NULL);
    
    //定义两个页面指针 p1 和 p2
    struct Page *p1, *p2;
    //分配一个页面 p1
    p1 = alloc_page();
    //将 p1 插入到虚拟地址 0x0
    assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0);

    // 获取虚拟地址 0x0 对应的页表项指针
    pte_t *ptep;
    assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL);
    // 验证页表项对应的页面是 p1
    assert(pte2page(*ptep) == p1);
    // 验证 p1 的引用计数为 1
    assert(page_ref(p1) == 1);
    // 获取虚拟地址 PGSIZE 对应的页表项指针
    ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1];
    assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep);
    // 分配一个页面 p2
    p2 = alloc_page();
    // 将 p2 插入到虚拟地址 PGSIZE，并设置用户和写权限
    assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0);
    // 获取虚拟地址 PGSIZE 对应的页表项指针
    assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL);
    // 验证页表项设置了用户权限
    assert(*ptep & PTE_U);
    // 验证页表项设置了写权限
    assert(*ptep & PTE_W);
    // 验证页目录项设置了用户权限
    assert(boot_pgdir[0] & PTE_U);
    // 验证 p2 的引用计数为 1
    assert(page_ref(p2) == 1);

    // 将 p1 插入到虚拟地址 PGSIZE，替换掉 p2
    assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0);
    // 验证 p1 的引用计数增加到 2
    assert(page_ref(p1) == 2);
    // 验证 p2 的引用计数减少到 0
    assert(page_ref(p2) == 0);
     // 获取虚拟地址 PGSIZE 对应的页表项指针
    assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL);
    // 验证页表项对应的页面是 p1
    assert(pte2page(*ptep) == p1);
     // 验证页表项没有设置用户权限
    assert((*ptep & PTE_U) == 0);
    
    //移除虚拟地址 0x0 的映射，
    page_remove(boot_pgdir, 0x0);
    //验证 p1 的引用计数减少到 1。
    assert(page_ref(p1) == 1);
    //验证 p2 的引用计数减少到 0
    assert(page_ref(p2) == 0);

    //移除虚拟地址 PGSIZE 的映射，
    page_remove(boot_pgdir, PGSIZE);
    //验证 p1 的引用计数减少到 0
    assert(page_ref(p1) == 0);
    //验证 p2 的引用计数减少到 0
    assert(page_ref(p2) == 0);
    
    //验证页目录的第一页表的引用计数为 1。
    assert(page_ref(pde2page(boot_pgdir[0])) == 1);
    //释放页目录的第一页表
    free_page(pde2page(boot_pgdir[0]));
    //清空页目录的第一页表
    boot_pgdir[0] = 0;

    cprintf("check_pgdir() succeeded!\n");
}

//检查内核页表 boot_pgdir 的正确性
static void
check_boot_pgdir(void) {
    pte_t *ptep;// 定义一个指向页表项的指针
    int i;
    for (i = 0; i < npage; i += PGSIZE) {// 遍历所有页面
        // 获取第 i 个页面的页表项，并确保其不为空
        assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL);
        // 验证页表项的物理地址是否正确
        assert(PTE_ADDR(*ptep) == i);
    }
    // 验证页目录项的物理地址是否正确
    assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir));

    assert(boot_pgdir[0] == 0);// 确保页目录的第一个项为0

    struct Page *p;// 定义一个指向页面的指针
    p = alloc_page();// 分配一个页面
    // 将页面插入到虚拟地址 0x100，并确保操作成功
    assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0);
    assert(page_ref(p) == 1);// 验证页面的引用计数为1
    // 将页面插入到虚拟地址 0x100 + PGSIZE，并确保操作成功
    assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0);
    assert(page_ref(p) == 2);// 验证页面的引用计数为2

    const char *str = "ucore: Hello world!!";// 定义一个字符串
    strcpy((void *)0x100, str);// 将字符串复制到虚拟地址 0x100
    // 验证两个映射地址的数据是否一致
    assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0);
    // 在页面的 0x100 偏移处设置字符串结束符
    *(char *)(page2kva(p) + 0x100) = '\0';
    assert(strlen((const char *)0x100) == 0);// 验证字符串长度为0

    free_page(p);// 释放页面 p
    free_page(pde2page(boot_pgdir[0]));// 释放页目录项对应的页面
    boot_pgdir[0] = 0;// 将页目录的第一个项设为0

    cprintf("check_boot_pgdir() succeeded!\n");// 输出成功信息
}

//perm2str - use string 'u,r,w,-' to present the permission
static const char *
perm2str(int perm) {
    //定义一个静态字符数组 str，长度为4
    static char str[4];
    //如果 perm 与 PTE_U 按位与的结果不为0，则 str[0] 设置为 'u'，否则设置为 '-'
    str[0] = (perm & PTE_U) ? 'u' : '-';
    //str[1] 始终设置为 'r'
    str[1] = 'r';
    //如果 perm 与 PTE_W 按位与的结果不为0，则 str[2] 设置为 'w'，否则设置为 '-'
    str[2] = (perm & PTE_W) ? 'w' : '-';
    //str[3] 设置为字符串结束符 \0
    str[3] = '\0';
    return str;
}

//get_pgtable_items - In [left, right] range of PDT or PT, find a continuous linear addr space
//                  - (left_store*X_SIZE~right_store*X_SIZE) for PDT or PT
//                  - X_SIZE=PTSIZE=4M, if PDT; X_SIZE=PGSIZE=4K, if PT
// paramemters:
//  left:        no use ???
//  right:       the high side of table's range
//  start:       the low side of table's range
//  table:       the beginning addr of table
//  left_store:  the pointer of the high side of table's next range
//  right_store: the pointer of the low side of table's next range
// return value: 0 - not a invalid item range, perm - a valid item range with perm permission 
//从页表中获取指定范围内的有效项，并根据权限进行处理。
static int
get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) {
    if (start >= right) {// 检查起始索引是否超出右边界
        return 0;// 如果超出右边界，返回0
    }
    while (start < right && !(table[start] & PTE_P)) {// 查找第一个有效项（PTE_P位为1的项）
        start ++;// 索引递增
    }
    if (start < right) {// 检查是否找到有效项
        if (left_store != NULL) {// 如果left_store不为NULL
            *left_store = start;// 记录左边界索引
        }
        int perm = (table[start ++] & PTE_USER);// 获取当前项的用户权限位并递增索引
        while (start < right && (table[start] & PTE_USER) == perm) {// 查找具有相同用户权限的连续项
            start ++;// 索引递增
        }
        if (right_store != NULL) {// 如果right_store不为NULL
            *right_store = start;// 记录右边界索引
        }
        return perm;// 返回用户权限位
    }
    return 0;// 如果未找到有效项，返回0
}

//print_pgdir - print the PDT&PT
void
print_pgdir(void) {
    cprintf("-------------------- BEGIN --------------------\n");
    // 定义变量 left, right 和 perm
    size_t left, right = 0, perm;
    // 遍历页目录项
    while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) {
        // 打印页目录项的信息
        //right - left：表示当前页目录项的索引范围；
        //left * PTSIZE 和 right * PTSIZE：表示当前页目录项对应的虚拟地址范围。
        //(right - left) * PTSIZE：表示当前页目录项覆盖的虚拟地址空间大小。
        //perm2str(perm)：将页目录项的标志位转换为字符串形式，以便于打印。
        cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left,
                left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm));
        // 计算页表项的起始和结束索引
        size_t l, r = left * NPTEENTRY;
        // 遍历页表项
        while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) {
            // 打印页表项的信息
            cprintf("  |-- PTE(%05x) %08x-%08x %08x %s\n", r - l,
                    l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm));
        }
    }
    cprintf("--------------------- END ---------------------\n");
}

